
#include "shaderDefs.h"

struct VSAttrs {
    vec4 Position;
    vec3 Normal;
    vec2 TexCoords;
};

// uniform sampler2D TexDisp;

sampler {
    MinFilter=trilinear;
    WrapS=mirroredRepeat;
    WrapT=mirroredRepeat;
    Aniso=4;
} TexDisp, TexDiffuse, TexNormal;

#define ID gl_InvocationID

interface VSOutputs {
    vec3 normal;
    vec2 texCoords;
};

void VSDisplaced(in VSAttrs VSIn:0, out VSOutputs vsout)
{
    gl_Position=GlobalMatrices.Camera*(LocalModel.ModelView*VSIn.Position);
    vsout.normal=GlobalMatrices.NormalC*(LocalModel.NormalM*VSIn.Normal);
    vsout.texCoords=VSIn.TexCoords;
}

void TCQuad(in VSOutputs vsout[], patch out vec3 cSu, patch out vec3 cSv, patch out vec3 cSn, patch out vec4 cLods,
              patch out mat3x2 TexCoordsMatLow, patch out mat3x2 TexCoordsMatHigh)
{
    // ID is 0
    // 2--3
    // |  |
    // |  |
    // 0--1
    mat4 coordsMat=GlobalMatrices.Projection*mat4(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position, gl_in[3].gl_Position);
    vec4 projectedz=transpose(coordsMat)[2];
#ifdef ZBUFFER
    vec4 projectedw=transpose(coordsMat)[3];
    projectedz/=projectedw;
    projectedz=projectedz*0.5+0.5;  // [-1,1] --> [0,1]

    vec4 lods=clamp(-log2(projectedz)*2000, 1, gl_MaxTessGenLevel);
    cLods=(1-lods/gl_MaxTessGenLevel);
    cLods*=8*cLods;
#else
    // We have a wbuffer, projectedz is already in [-1,1]
    vec4 t=smoothstep(-1,1,projectedz);
    vec4 lods=gl_MaxTessGenLevel*(1-t);
    cLods=8*t;
#endif

    lods=max(lods,lods.yyzw);
    lods=max(lods,lods.zzzw);
    lods=max(lods,lods.wwww);
    float lod=lods.x;
    gl_TessLevelInner[0]=lod;
    gl_TessLevelInner[1]=lod;
    gl_TessLevelOuter[0]=lod;
    gl_TessLevelOuter[1]=lod;
    gl_TessLevelOuter[2]=lod;
    gl_TessLevelOuter[3]=lod;

    gl_out[ID].gl_Position=gl_in[0].gl_Position;
    cSu = (gl_in[1].gl_Position-gl_in[0].gl_Position).xyz;
    cSv = (gl_in[2].gl_Position-gl_in[0].gl_Position).xyz;
    cSn = normalize(cross(cSu, cSv));
    TexCoordsMatLow=mat3x2(vsout[1].texCoords-vsout[0].texCoords,vsout[3].texCoords-vsout[1].texCoords,vsout[0].texCoords);
    TexCoordsMatHigh=mat3x2(vsout[3].texCoords-vsout[2].texCoords,vsout[2].texCoords-vsout[0].texCoords,vsout[0].texCoords);
}

interface FSAttrs {
    vec3 Normal;
    vec3 Location;
    vec2 TexCoords;
};

void TEQuad(patch in vec3 cSu, patch in vec3 cSv, patch in vec3 cSn, patch in vec4 cLods, out FSAttrs TEOut,
              patch in mat3x2 TexCoordsMatLow, patch in mat3x2 TexCoordsMatHigh)
{
    vec4 origin=gl_in[0].gl_Position;

    // compute lod
    // 2--3
    // |  |
    // |  |
    // 0--1
    vec4 xcrds=vec4(0,1,0,1)-gl_TessCoord.xxxx;
    vec4 ycrds=vec4(0,0,1,1)-gl_TessCoord.yyyy;
    xcrds*=xcrds;
    ycrds*=ycrds;
    vec4 dsts=sqrt(xcrds+ycrds);
    dsts/=dot(dsts,vec4(1));
    float lod=dot(dsts,cLods);

    // sample position and etc...
    float scale=Material.extra.x;
    mat3x2 texCoordsMat=gl_TessCoord.x < gl_TessCoord.y ? TexCoordsMatHigh : TexCoordsMatLow;
    vec2 texCoords=texCoordsMat*vec3(gl_TessCoord.xy,1);
    vec3 pos=gl_TessCoord.x*cSu + gl_TessCoord.y*cSv + textureLod(TexDisp, texCoords, lod).a*scale*cSn;

    vec4 loc=origin+vec4(pos, 0);
    TEOut.Location=loc.xyz;
    gl_Position=ProjectVertex(GlobalMatrices.Projection, loc);
    
    vec2 SvSu=vec2(length(cSv), length(cSu));
    vec2 ratios=SvSu.xy/SvSu.yx;
    vec2 dh=vec2(   textureLodOffset(TexDisp, texCoords, lod, ivec2(1,0)).a-textureLodOffset(TexDisp, texCoords, lod, ivec2(-1,0)).a,
                    textureLodOffset(TexDisp, texCoords, lod, ivec2(0,1)).a-textureLodOffset(TexDisp, texCoords, lod, ivec2(0,-1)).a);
    vec2 dispSize=vec2(textureSize(TexDisp, int(lod)));
    dispSize*=0.5*dh*ratios;
    TEOut.Normal=SvSu.x*SvSu.y*cSn-scale*(dispSize.x*cSu+dispSize.y*cSv);
    TEOut.TexCoords=texCoords;
}

void FSQuad(in FSAttrs FSIn, out vec4 fColor:0)
{
    vec3 normal=normalize(FSIn.Normal);
    vec4 outColor=vec4(0,0,0,1);
    outColor.rgb=Lighting(normal, FSIn.Location, Material.diffuse);

    fColor=outColor;
    // gl_FragDepth=gl_FragCoord.z*gl_FragCoord.z;
}

void FSTextured(in FSAttrs FSIn, out vec4 fColor:0)
{
    vec3 normal=normalize(FSIn.Normal);
    vec4 outColor=vec4(0,0,0,1);
    outColor.rgb=Lighting(normal, FSIn.Location, texture(TexDiffuse, FSIn.TexCoords), Material.diffuse.a);

    fColor=outColor;    
}



interface TCTriData {
    vec3 normal;
    vec2 texCoords;
};

void TCDisplacedTri(in VSOutputs fromVS[], out TCTriData forTE[], patch out vec3 cLods)
{
    mat3x4 coordsMat=GlobalMatrices.Projection*mat3x4(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_in[2].gl_Position);
    vec3 projectedz=transpose(coordsMat)[2];
#ifdef ZBUFFER
    vec3 projectedw=transpose(coordsMat)[3];
    projectedz/=projectedw;
    projectedz=projectedz*0.5+0.5;  // [-1,1] --> [0,1]

    vec3 lods=clamp(-log2(projectedz)*2000, 1, gl_MaxTessGenLevel);
    cLods=(1-lods/gl_MaxTessGenLevel);
    cLods*=8*cLods;
#else
    // We have a wbuffer, projectedz is already in [-1,1]
    vec3 t=smoothstep(-1,1,projectedz);
    vec3 lods=gl_MaxTessGenLevel*(1-t);
    cLods=8*t;
#endif

    float lod=max(lods.x, max(lods.y, lods.z));
    gl_TessLevelInner[0]=lod;
    gl_TessLevelOuter[0]=lod;
    gl_TessLevelOuter[1]=lod;
    gl_TessLevelOuter[2]=lod;
    
    vec4 tmp=gl_InvocationID==0 ? vec4(0) : gl_in[0].gl_Position;
    gl_out[gl_InvocationID].gl_Position=gl_in[gl_InvocationID].gl_Position-tmp;
    tmp=gl_InvocationID==0 ? vec4(0) : fromVS[0].normal;
    forTE[gl_InvocationID].normal=fromVS[gl_InvocationID].normal-tmp;
    tmp=gl_InvocationID==0 ? vec4(0) : fromVS[0].texCoords;
    forTE[gl_InvocationID].texCoords=fromVS[gl_InvocationID].texCoords-tmp;
}

void TEDisplacedTri(in TCTriData fromTC[], patch in vec3 cLods, out FSAttrs forFS)
{
    vec4 origin=gl_in[0].gl_Position;
    vec3 S_u=gl_in[1].gl_Position.xyz;
    vec3 S_v=gl_in[2].gl_Position.xyz;
    vec3 n0=fromTC[0].normal;
    vec3 n_u=fromTC[1].normal;
    vec3 n_v=fromTC[2].normal;
    vec2 st0=fromTC[0].texCoords;
    vec2 st_u=fromTC[1].texCoords;
    vec2 st_v=fromTC[2].texCoords;
    
    // in uv space the triangle looks like this, so it's a breeze
    // 2
    // |\
    // | \
    // 0--1

    // compute lod
    float lod=dot(cLods, gl_TessCoord);
    
    // sample position and etc...
    float scale=Material.extra.x;
    vec3 normal=n0 + gl_TessCoord.x*n_u + gl_TessCoord.y*n_v;
    float normSq=dot(normal, normal);
    float normInv=inversesqrt(normSq);
    normal*=normInv;
    n_u-=normal*dot(normal, n_u);
    n_u*=normInv;
    n_v-=normal*dot(normal, n_v);
    n_v*=normInv;
    vec2 texCoords=st0 + gl_TessCoord.x*st_u + gl_TessCoord.y*st_v;
    float h=textureLod(TexDisp, texCoords, lod).r*scale;
    vec4 loc=origin + vec4(gl_TessCoord.x*S_u + gl_TessCoord.y*S_v + h*normal, 0);

    TEOut.Location=loc.xyz;
    gl_Position=ProjectVertex(GlobalMatrices.Projection, loc);
    
    vec2 h_xy=vec2( textureLodOffset(TexDisp, texCoords, lod, ivec2(1,0)).r-textureLodOffset(TexDisp, texCoords, lod, ivec2(-1,0)).r,
                    textureLodOffset(TexDisp, texCoords, lod, ivec2(0,1)).r-textureLodOffset(TexDisp, texCoords, lod, ivec2(0,-1)).r) * 0.5 * scale;
    vec2 h_st=h_xy*vec2(textureSize(TexDisp, int(lod)));
    vec2 h_uv=h_st*mat2(st_u, st_v);
    TEOut.Normal=cross(S_u + n_u*h + normal*h_uv.x, S_v + n_v*h + normal*h_uv.y);
    TEOut.TexCoords=texCoords;
}



program DisplacedQuad {
    vs(420)=VSDisplaced();
    tc(420)=TCQuad() : out(vertices=1);
    te(420)=TEQuad() : in(quads, equal_spacing, ccw);
    fs(420)=FSQuad();
};

program DisplacedQuadTextured {
    vs(420)=VSDisplaced();
    tc(420)=TCQuad() : out(vertices=1);
    te(420)=TEQuad() : in(quads, equal_spacing, ccw);
    fs(420)=FSTextured();
};

program DisplacedTriTextured {
    vs(420)=VSDisplaced();
    tc(420)=TCDisplacedTri() : out(vertices=3);
    te(420)=TEDisplacedTri() : in(triangles, equal_spacing, ccw);
    fs(420)=FSTextured();
};
